﻿using RockBus.Monitoring.DataContracts;
using System;
using System.Diagnostics;

namespace RockBus.Monitoring
{
    /// <summary>
    /// The struct that represents the consistent state of the RoundRobinBuffer.
    ///  To be retrieved with the RoundRobinBuffer.GetState() method.
    /// </summary>
    [Serializable]
    public struct RoundRobinBufferState
    {
        public RoundRobinBufferState(double current, double movingTotal, double movingAverage, double movingMin, double movingMax)
        {
            this.Current = current;
            this.MovingTotal = movingTotal;
            this.MovingAverage = movingAverage;
            this.MovingMin = movingMin;
            this.MovingMax = movingMax;
        }

        public double Current;
        public double MovingTotal;
        public double MovingAverage;
        public double MovingMin;
        public double MovingMax;
    }

    /// <summary>
    /// Threadsafe buffer that contains the last n values that were added, where n = BuferSize. Round robins over n values, discarding any old values. Calculates moving aggregated values on the fly.
    /// </summary>
    [Serializable]
    public class RoundRobinBuffer : ICloneable
    {
        private readonly object _lockObject = new object();

        public RoundRobinBuffer(int size)
        {
            _buffer = new double[size];
            _size = size;
        }

        private readonly double[] _buffer;

        private double[] Buffer
        {
            get { return _buffer; }
        }

        private int _index;

        private int Index
        {
            get { return _index; }
            set { _index = value; }
        }

        /// <summary>
        /// Retrieves the consistent state of the RoundRobinBuffer.
        /// </summary>
        /// <returns>The consistent state of the RoundRobinBuffer.</returns>
        public RoundRobinBufferState GetState()
        {
            lock (_lockObject)
            {
                return new RoundRobinBufferState(this.Current, this.MovingTotal, this.MovingAverage, this.MovingMin, this.MovingMax);
            }
        }

        private readonly int _size;

        /// <summary>
        /// The size of the buffer.
        /// </summary>
        public int Size
        {
            get { return _size; }
        }

        private void MoveNext()
        {
            this.Index++;
            this.Index %= this.Size;
        }

        /// <summary>
        /// The current (latest) value within the buffer.
        /// </summary>
        public double Current
        {
            get
            {
                return this.Buffer[this.Index];
            }
            private set { this.Buffer[this.Index] = value; }
        }

        private double _movingTotal;

        /// <summary>
        /// The moving total of the values within the buffer.
        /// </summary>
        public double MovingTotal
        {
            get
            {
                return _movingTotal;
            }
            private set { _movingTotal = value; }
        }

        private double _movingAverage;

        /// <summary>
        /// The moving average of the values within the buffer.
        /// </summary>
        public double MovingAverage
        {
            get
            {
                return _movingAverage;
            }
            private set { _movingAverage = value; }
        }

        private double? _min;

        /// <summary>
        /// The moving minimum of the values within the buffer.
        /// </summary>
        public double MovingMin
        {
            get
            {
                if (!_min.HasValue)
                {
                    _min = double.MaxValue;
                    foreach (double d in this.Buffer)
                    {
                        _min = Math.Min(_min.Value, d);
                    }
                }
                return _min.Value;
            }
        }

        private double? _max;

        /// <summary>
        /// The moving maximum of the values within the buffer.
        /// </summary>
        public double MovingMax
        {
            get
            {
                if (!_max.HasValue)
                {
                    _max = double.MinValue;
                    foreach (double d in this.Buffer)
                    {
                        _max = Math.Max(_max.Value, d);
                    }
                }
                return _max.Value;
            }
        }

        /// <summary>
        /// Adds a new value to the buffer, whilst discarding the oldest value in the buffer (within an atomic operation). Updates the moving aggregated values.
        /// </summary>
        /// <param name="value"></param>
        public void AddValue(double value)
        {
            this.MoveNext();
            this.MovingTotal += value - this.Current;
            this.MovingAverage = this.MovingTotal / this.Size;
            this.Buffer[this.Index] = value;
            _min = null;
            _max = null;
        }

        private CounterSample _lastCounterSample;

        public void AddCounterSample(CounterSample counterSample)
        {
            if ((null != _lastCounterSample) && (counterSample.TimeStamp100nSec > _lastCounterSample.TimeStamp100nSec))
            {
                float sampleValue = CounterSampleCalculator.ComputeCounterValue(_lastCounterSample, counterSample);
                //this.DebugWriteManuallyCalculatedValue(_lastCounterSample, counterSample, sampleValue);
                this.AddValue(sampleValue);
            }

            _lastCounterSample = counterSample;
        }

        [Conditional("DEBUG")]
        private void DebugWriteManuallyCalculatedValue(CounterSample previous, CounterSample current, float calculated)
        {
            long N0 = previous.RawValue;
            long N1 = current.RawValue;

            long D0 = previous.TimeStamp100nSec;
            long D1 = current.TimeStamp100nSec;

            float result = (1 - (N1 - N0) / (D1 - D0)) * 100;
            Debug.WriteLine(string.Format("{0},{1},{2},{3},{4},{5}",
                calculated,
                previous.RawValue,
                previous.BaseValue,
                current.RawValue,
                current.BaseValue,
                result));
        }

        public double GetValue(AggregationTypeEnum? at)
        {
            double value = 0.0;
            AggregationTypeEnum atTemp = AggregationTypeEnum.LastValue;
            if (at.HasValue)
            {
                atTemp = at.Value;
            }

            switch (atTemp)
            {
                case AggregationTypeEnum.LastValue:
                    value = this.Current;
                    break;

                case AggregationTypeEnum.MovingAverage:
                    value = this.MovingAverage;
                    break;

                case AggregationTypeEnum.MovingMax:
                    value = this.MovingMax;
                    break;

                case AggregationTypeEnum.MovingMin:
                    value = this.MovingMin;
                    break;

                case AggregationTypeEnum.MovingTotal:
                    value = this.MovingTotal;
                    break;
            }

            return value;
        }

        public object Clone()
        {
            RoundRobinBuffer rrb = new RoundRobinBuffer(this.Size);
            foreach (double val in this.Buffer)
            {
                Array.Copy(this.Buffer, rrb._buffer, this.Size);
                rrb._index = this._index;
                rrb._movingAverage = this._movingAverage;
                rrb._movingTotal = this._movingTotal;
            }
            return rrb;
        }
    }
}